package com.minglead.springboot.common.utils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.MappedPropertyDescriptor;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.util.Assert;

/**
 * 扩展spring的BeanUtils，增加拷贝属性排除null值的功能(注：String为null不考虑)
 */
public class BeanUtils extends org.springframework.beans.BeanUtils {

	public static void copyNotNullProperties(Object source, Object target, String[] ignoreProperties) throws BeansException {
		copyNotNullProperties(source, target, null, ignoreProperties);
	}

	public static void copyNotNullProperties(Object source, Object target, Class<?> editable) throws BeansException {
		copyNotNullProperties(source, target, editable, null);
	}

	public static void copyNotNullProperties(Object source, Object target) throws BeansException {
		copyNotNullProperties(source, target, null, null);
	}

	private static void copyNotNullProperties(Object source, Object target, Class<?> editable, String[] ignoreProperties) throws BeansException {

		Assert.notNull(source, "Source must not be null");
		Assert.notNull(target, "Target must not be null");

		Class<?> actualEditable = target.getClass();
		if (editable != null) {
			if (!editable.isInstance(target)) {
				throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]");
			}
			actualEditable = editable;
		}
		PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
		List<String> ignoreList = (ignoreProperties != null) ? Arrays.asList(ignoreProperties) : null;

		for (PropertyDescriptor targetPd : targetPds) {
			if (targetPd.getWriteMethod() != null && (ignoreProperties == null || (!ignoreList.contains(targetPd.getName())))) {
				PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
				if (sourcePd != null && sourcePd.getReadMethod() != null) {
					try {
						Method readMethod = sourcePd.getReadMethod();
						if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
							readMethod.setAccessible(true);
						}
						Object value = readMethod.invoke(source);
						if (value != null || readMethod.getReturnType().getName().equals("java.lang.String")) {// 这里判断以下value是否为空，当然这里也能进行一些特殊要求的处理 例如绑定时格式转换等等，如果是String类型，则不需要验证是否为空
							boolean isEmpty = false;
							if (value instanceof Set) {
								Set s = (Set) value;
								if (s == null || s.isEmpty()) {
									isEmpty = true;
								}
							} else if (value instanceof Map) {
								Map m = (Map) value;
								if (m == null || m.isEmpty()) {
									isEmpty = true;
								}
							} else if (value instanceof List) {
								List l = (List) value;
								if (l == null || l.size() < 1) {
									isEmpty = true;
								}
							} else if (value instanceof Collection) {
								Collection c = (Collection) value;
								if (c == null || c.size() < 1) {
									isEmpty = true;
								}
							}
							if (!isEmpty) {
								Method writeMethod = targetPd.getWriteMethod();
								if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
									writeMethod.setAccessible(true);
								}
								writeMethod.invoke(target, value);
							}
						}
					} catch (Throwable ex) {
						throw new FatalBeanException("Could not copy properties from source to target", ex);
					}
				}
			}
		}
	}
	
	/**
	 * 属性设置；寻找set方法（也可以直接设置属性的值的，不需要通过set方法，后期改造吧）
	 * @param bean
	 * @param properties
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * 类似org.apache.commons.beanutils.BeanUtils.populate
	 * @throws ParseException 
	 * @throws IllegalArgumentException 
	 */
    public static void populate(Object bean, Map properties)
            throws IllegalAccessException, InvocationTargetException, IllegalArgumentException, ParseException { 	
    	
    	if(bean==null||properties==null)
    		return;
    	
    	Class<?> clazz = bean.getClass();
		Method methods[] = clazz.getDeclaredMethods();
    	
        Iterator entries = properties.entrySet().iterator();
        while (entries.hasNext()) {
        	
            Map.Entry entry = (Map.Entry)entries.next();
            String name = (String) entry.getKey();
            if (name == null) {
                continue;
            }
            String setMethodName= "set"+StringUtils.toUpperCaseFirstOne(name);
            
            
            String[] valueArray =  (String[]) entry.getValue();
            String value = valueArray[0];
            
            if (StringUtils.isEmpty(value)) {
                continue;
            }
            
            //遍历，是否存在setXXX 方法，如果存在，调用设值(也可以直接设置字段的值：field.set(entity,"xxx")
            for(Method m:methods){
            	if(m.getName().equals(setMethodName)){
            		String fieldType = m.getParameterTypes()[0].getName();
					//以下可以确认value为String类型
					if(String.class.getName().equals(fieldType)){
						m.invoke(bean, (String)value);
					}else if(Integer.class.getName().equals(fieldType) && NumberUtils.isDigits((String)value)){
						m.invoke(bean, Integer.valueOf((String)value));
					}else if(Short.class.getName().equals(fieldType) && NumberUtils.isDigits((String)value)){
						m.invoke(bean, Short.valueOf((String)value));
					}else if(Float.class.getName().equals(fieldType) && NumberUtils.isNumber((String)value)){
						m.invoke(bean, Float.valueOf((String)value));
					}else if(Double.class.getName().equals(fieldType) && NumberUtils.isNumber((String)value)){
						m.invoke(bean, Double.valueOf((String)value));
					}else if(Date.class.getName().equals(fieldType)){
						m.invoke(bean, DateUtils.parseDate((String)value, "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss"));
					}else if(Boolean.class.getName().equals(fieldType)){
						m.invoke(bean, Boolean.parseBoolean(value));
					}else{
						m.invoke(bean, value);
					}
					     		
            	}
            }
    }	
    }

}
